/*
 * Copyright 2020-2030 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wwl.linked.list.linkedList;

import javafx.util.Pair;

import java.util.Objects;

/**
 * @author Neil Wang
 * @version 1.0.0
 * @date 2021/6/20 4:29 下午
 */
public class RecursiveLinkedList<T> {

    private Node<T> node;
    private int size;

    public RecursiveLinkedList() {
    }

    public int size() {
        return size;
    }

    public boolean empty() {
        return size == 0;
    }

    public void addFirst(T t) {
        add(0, t);
    }

    public void add(int index, T t) {
        if (index < 0 || index > size) throw new IndexOutOfBoundsException("Add failed. Illegal index.");
        node = add(node, index, t);
        size++;
    }

    private Node<T> add(Node<T> node, int index, T t) {
        if (index == 0) return new Node<>(t, node);
        node.setNext(add(node.next(), --index, t));
        return node;
    }

    public void addLast(T t) {
        add(size, t);
    }

    public T get(int index) {
        if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Get failed. Illegal index.");
        return get(node, index);
    }

    private T get(Node<T> node, int index) {
        if (index == 0) return node.data();
        return get((Node<T>) node.next(), --index);
    }

    public T getFirst() {
        return get(0);
    }

    public T getLast() {
        return get(size - 1);
    }

    public void set(int index, T t) {
        if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Set failed. Illegal index.");
        set(node, index, t);
    }

    private void set(Node<T> node, int index, T t) {
        if (index == 0) {
            node.setE(t);
            return;
        }
        set(node.next(), --index, t);
    }

    public void setFirst(T t) {
        set(0, t);
    }

    public void setLast(T t) {
        set(size - 1, t);
    }

    public boolean contains(T t) {
        return contains(node, t);
    }

    private boolean contains(Node<T> node, T t) {
        if (node == null) return false;
        if (Objects.equals(node.data(), t)) return true;
        return contains(node.next(), t);
    }

    public T remove(int index) {
        if (index < 0 || index >= size) throw new IndexOutOfBoundsException("Set failed. Illegal index.");
        Pair<Node<T>, T> res = remove(node, index);
        size--;
        node = res.getKey();
        return res.getValue();
    }

    private Pair<Node<T>, T> remove(Node<T> node, int index) {
        if (index == 0) return new Pair<>(node.next(), node.data());
        Pair<Node<T>, T> next = remove(node.next(), --index);
        node.setNext(next.getKey());
        return new Pair<>(node, next.getValue());
    }

    public T removeFirst() {
        return remove(0);
    }

    public T removeLast() {
        return remove(size - 1);
    }

    public void removeElement(T t) {
        node = removeElement(node, t);
    }

    private Node<T> removeElement(Node<T> node, T t) {
        if (node == null) return null;
        if (Objects.equals(node.data(), t)) {
            size--;
            return node.next();
        }
        node.setNext(removeElement(node.next(), t));
        return node;
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("RecursivedLinkedList: top -> ");
        return toString(res, node);
    }

    private String toString(StringBuilder stringBuilder, Node<T> node) {
        if (node == null) return stringBuilder.append("last").toString();
        return toString(stringBuilder.append(node.data()).append(" -> "), node.next());
    }

}
